package part7

import (
	"bytes"
	"crypto/ecdsa"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"os"
)

const dbFile = "blockchain_db.json"

//区块链
type Blockchain struct {
	//map[区块的hash] = 区块
	DB map[string]Block `json:"DB"`
	// l 存最新的区块hash值
	L string `json:"L"`
}

func CreateBlockchain(address string) *Blockchain {
	if !PathExists(dbFile) {
		//如果文件不存在，则创建一个blockchain的json文件
		f, err := os.Create(dbFile)
		if err != nil {
			fmt.Println(err.Error())
		}
		defer f.Close()
	}

	//创世区块
	firstAddress := address
	cbtx := NewCoinbaseTX(firstAddress, "")
	block := NewGenesisBlock(cbtx)
	var db map[string]Block = map[string]Block{}
	hash := hex.EncodeToString(block.Hash)
	db[hash] = *block
	bc := &Blockchain{
		DB: db,
		L:  hash,
	}
	//写入文件
	data, _ := json.Marshal(bc)
	err := ioutil.WriteFile(dbFile, data, 0666)
	if err != nil {
		fmt.Println("write fail")
	}
	return bc
}

//获得区块链
func GetBlockchain() *Blockchain {
	if !PathExists(dbFile) {
		log.Fatal("请先创建区块链")
		return nil
	}

	content, err := ioutil.ReadFile(dbFile)
	if err != nil {
		panic(err)
	}
	var bc Blockchain
	err = json.Unmarshal(content, &bc)
	if err != nil {
		panic(err)
	}
	return &bc
}

//save区块链
func (bc *Blockchain) SaveBlockchain() *Blockchain {
	if !PathExists(dbFile) {
		log.Fatal("请先创建区块链")
		return nil
	}

	//写入文件
	data, _ := json.Marshal(bc)
	err := ioutil.WriteFile(dbFile, data, 0666)
	if err != nil {
		fmt.Println("write fail")
	}
	return bc
}

//给某个地址发送金额
func (bc *Blockchain) Send(from, to string, amount int) {
	wallets, _ := NewWallets()
	wallet := wallets.GetWallet(from)

	tx := NewUTXOTransaction(&wallet, to, amount, bc)
	transactions := make([]Transaction, 0)
	//挖矿奖励
	cbtx := NewCoinbaseTX(from, "")
	transactions = append(transactions, cbtx)
	transactions = append(transactions, tx)
	bc.AddBlock(transactions)
}

//余额
func (bc *Blockchain) GetBalance(address string) int {
	balance, _, _ := bc.FindSpendableOutputs(address)
	fmt.Printf("balance of %s is %d\n", address, balance)
	return balance
}

//添加区块
func (bc *Blockchain) AddBlock(transactions []Transaction) {
	//验证交易
	//在序列化过程中，有会一个字节的转变，出现问题
	for _, tx := range transactions {
		if !bc.VerifyTransaction(&tx) {
			log.Panic("ERROR: Invalid transaction")
			return
		}
	}
	log.Println("transactions vadlid success")

	//最新的区块
	prevBlock := bc.DB[bc.L]
	//新建区块
	newBlock := NewBlock(transactions, prevBlock.Hash, prevBlock.Height+1)
	hash := newBlock.GetHashString()
	bc.DB[hash] = *newBlock
	bc.L = hash
	//保存
	bc.SaveBlockchain()
}

//创世区块
func NewGenesisBlock(coinbase Transaction) *Block {
	// return NewBlock("Genesis Block", []byte{}, 0)
	newBlock := NewBlock([]Transaction{coinbase}, []byte{}, 0)
	return newBlock
}

//找到没有花费的output集合，并返回全部余额
//返回：全部余额，可用的outputIndex，可用的output
func (bc *Blockchain) FindSpendableOutputs(address string) (int, map[string][]int, map[string][]TXOutput) {
	bci := bc.Iterator()
	//未使用output
	unspentOutputIndexList := make(map[string][]int)
	unspentOutputMap := make(map[string][]TXOutput)
	//已经使用的output
	spentTXOs := make(map[string][]int)
	accumulated := 0

	for {
		//循环所有的区块
		block := bci.Next()
		for _, tx := range block.Transactions {
			txID := hex.EncodeToString(tx.ID)
			//遍历交易的ouput
			for i, out := range tx.Vout {
				flag := true
				if spentTXOs[txID] != nil {
					for _, spentOut := range spentTXOs[txID] {
						if spentOut == i {
							//这个output已使用
							flag = false
							break
						}
					}
				}
				if !flag {
					//下一个output
					break
				}
				//未使用的output，对比地址是否属于自己
				if bytes.Equal(out.Pubkeyhash, AddressToPubHash([]byte(address))) {
					//这个output是可以使用的
					unspentOutputIndexList[txID] = append(unspentOutputIndexList[txID], i)
					accumulated += out.Value
					unspentOutputMap[txID] = append(unspentOutputMap[txID], out)
				}

			}
			//使用input来得到已经使用的output
			if !tx.IsCoinbase() {
				for _, in := range tx.Vin {
					inTxID := hex.EncodeToString(in.Txid)
					spentTXOs[inTxID] = append(spentTXOs[inTxID], in.Vout)
				}
			}

		}
		if len(block.PrevBlockHash) == 0 {
			break
		}
	}
	return accumulated, unspentOutputIndexList, unspentOutputMap
}

//签名交易
func (bc *Blockchain) SignTransaction(tx *Transaction, privKey ecdsa.PrivateKey) {
	prevTXs := make(map[string]Transaction)
	for _, vin := range tx.Vin {
		prevTX, err := bc.FindTransaction(vin.Txid)
		if err != nil {
			log.Println(err)
		} else {
			prevTXs[hex.EncodeToString(prevTX.ID)] = prevTX
		}
	}
	tx.Sign(privKey, prevTXs)
}

//验证交易
func (bc *Blockchain) VerifyTransaction(tx *Transaction) bool {
	prevTXs := make(map[string]Transaction)
	for _, vin := range tx.Vin {
		prevTX, err := bc.FindTransaction(vin.Txid)
		if err != nil {
			log.Println(err)
		} else {
			prevTXs[hex.EncodeToString(prevTX.ID)] = prevTX
		}
	}
	return tx.Verify(prevTXs)
}

//查询交易
func (bc *Blockchain) FindTransaction(ID []byte) (Transaction, error) {
	bci := bc.Iterator()
	for {
		block := bci.Next()
		for _, tx := range block.Transactions {
			if bytes.Equal(tx.ID, ID) {
				return tx, nil
			}
		}
		if len(block.PrevBlockHash) == 0 {
			break
		}
	}
	return Transaction{}, errors.New("Transaction is not found")
}

//打印区块链
func (bc *Blockchain) Print() {
	bci := bc.Iterator()
	for {
		//循环所有的区块
		block := bci.Next()
		fmt.Printf(" %v\n", block)
		if len(block.PrevBlockHash) == 0 {
			break
		}
	}
}
